package com.example.autumn.jdbc;

import com.example.autumn.exeception.DataAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.*;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author liuzhiyong
 * @date 2023/11/3
 * description: 数据库查询结果和Bean的映射
 */
public class BeanRowMapper<T> implements RowMapper<T> {

    final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 要映射的类
     */
    Class<?> clazz;

    /**
     * 要映射的类的构造函数
     */
    Constructor<T> constructor;

    /**
     * 要映射的类的字段
     */
    Map<String, Field> fields = new HashMap<>();

    /**
     * Bean中的set方法
     * 把key设置成要set的属性值
     * 例如: setName(String name) {this.name = name;} 函数 key为nam
     */
    Map<String, Method> methods = new HashMap<>();

    /**
     * 构造函数
     * 初始化属性
     *
     * @author liuzhiyong
     * @date 2023/11/6
     */
    public BeanRowMapper(Class<T> clazz) {
        this.clazz = clazz;
        try {
            // 获取构造函数
            this.constructor = clazz.getConstructor();
        } catch (ReflectiveOperationException e) {
            throw new DataAccessException(String.format("No public default constructor found for class %s when build BeanRowMapper.", clazz.getName()), e);
        }
        // 获取属性
        for (Field f : clazz.getFields()) {
            String name = f.getName();
            this.fields.put(name, f);
            logger.atDebug().log("Add row mapping: {} to field {}", name, name);
        }
        // 获取set函数, 将添加进map中的可以设置成set的属性名
        for (Method m : clazz.getMethods()) {
            // 获取函数的参数
            Parameter[] ps = m.getParameters();
            // set函数的参数只有一个
            if (ps.length == 1) {
                // 获取函数名
                String name = m.getName();
                // 名称长度大于等于4,并且开头是set
                if (name.length() >= 4 && name.startsWith("set")) {
                    // 将set函数名转为属性名 例如 setName(String name) ==> name
                    String prop = Character.toLowerCase(name.charAt(3)) + name.substring(4);
                    this.methods.put(prop, m);
                    logger.atDebug().log("Add row mpping: {} to {}({})", prop, name, ps[0].getType().getSimpleName());
                }
            }
        }
    }

    @Override
    public T mapRow(ResultSet rs, int rowNum) throws SQLException {
        T bean;
        try {
            bean = this.constructor.newInstance();
            ResultSetMetaData meta = rs.getMetaData();
            int columns = meta.getColumnCount();
            //TODO 为什么从1 开始?
            for (int i = 1; i <= columns; i++) {
                // 获取列的标签, 由 SQL中的AS 指定
                String label = meta.getColumnLabel(i);
                // 获取set方法
                Method method = this.methods.get(label);
                if (method != null) {
                    // 执行set方法
                    method.invoke(bean, rs.getObject(label));
                } else {
                    // TODO? 没有set函数也需要反射填充属性吗? ==> 感觉不用
                    Field field = this.fields.get(label);
                    if (field != null) {
                        field.set(bean, rs.getObject(label));
                    }
                }
            }
        } catch (ReflectiveOperationException e) {
            throw new DataAccessException(String.format("Could not map result set to class %s", this.clazz.getName()), e);
        }
        return bean;
    }

}
